﻿using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using housingsafety.server.Entities;
using housingsafety.server.Entities.DataModels;
using housingsafety.server.Helpers;
using housingsafety.server.Models;
using housingsafety.server.Repository;
using housingsafety.server.Svcs;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;

namespace housingsafety.server.SvcImpls
{
    /// <summary>
    /// Token授权
    /// </summary>
    public class TokenAuthenticationService : IAuthenticateService
    {
        private readonly IUserTokenService _userService;
        private readonly TokenManagement _tokenManagement;
        private readonly IRepository<ZmUser> _userRepository;
        private readonly ZmDbContext _dbContext;
        private readonly IRepository<ZmBuildings> _buildingRepository;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="userService"></param>
        /// <param name="tokenManagement"></param>
        public TokenAuthenticationService(IUserTokenService userService, ZmDbContext dbContext,IOptions<TokenManagement> tokenManagement, IRepository<ZmUser> userRepository, IRepository<ZmBuildings> buildingRepository)
        {
            _dbContext = dbContext;
            _userService = userService;
            _tokenManagement = tokenManagement.Value;
            _userRepository = userRepository;
            _buildingRepository = buildingRepository;
        }

        public RespRet<AuthenticateResponse> Authenticate(AuthenticateRequest model, string ipAddress)
        {
            var user = _userService.ValidateUser(model, ipAddress);
            // return null if user not found
            if (user == null)
            {
                return new RespRet<AuthenticateResponse>(false, "用户不存在");
            }

            if (!user.Succ)
            {
                return new RespRet<AuthenticateResponse>(false, user.Msg);
            }

            return Authenticate(user.Data, ipAddress);
        }

        //房屋编号登录
        //public RespRet<AuthenticateResponse> AuthenticateFwbh(string fwbh, string ipAddress)
        //{
        //    var user = _userService.ValidateUserFwbh(fwbh, ipAddress);
        //    return null if user not found
        //    if (user == null)
        //    {
        //        return new RespRet<AuthenticateResponse>(false, "用户不存在");
        //    }

        //    if (!user.Succ)
        //    {
        //        return new RespRet<AuthenticateResponse>(false, user.Msg);
        //    }

        //    return Authenticate(user.Data, ipAddress);
        //}

      
        public RespRet<AuthenticateResponse> AuthenticateId(string fwbh, string ipAddress, bool ishaveuser)
        {

            var user = _userRepository.Query(c => c.Uname == fwbh&&c.Disabled!=1).FirstOrDefault();
            if (user == null)
            {
                return new RespRet<AuthenticateResponse>(false, "请登录"
                , new AuthenticateResponse(fwbh, "请登录", "请登录", "请登录", "请登录"));
            }
            if (string.IsNullOrEmpty(fwbh))
            {
                return new RespRet<AuthenticateResponse>(false, "userNumber is null");
            }

            var cached = _userService.GetRefreshTokens(fwbh);

            // authentication successful so generate jwt and refresh tokens
            var jwtToken = GenerateJwtToken(fwbh);
            var refreshToken = GenerateRefreshToken(fwbh, ipAddress);

            // save refresh token
            if (cached.Succ)
            {
                cached.Data.Tokens.Add(jwtToken.Sha256(), jwtToken);
                cached.Data.RefreshTokens.Add(refreshToken.Token.Sha256(), refreshToken);
                _userService.UpdateRefreshTokens(cached.Data);
            }
            else
            {
                var userRefreshToken = new UserRefreshToken(fwbh);
                userRefreshToken.Tokens.Add(jwtToken.Sha256(), jwtToken);
                userRefreshToken.RefreshTokens.Add(refreshToken.Token.Sha256(), refreshToken);
                _userService.UpdateRefreshTokens(userRefreshToken);
            }
            var dmLoginLog = new ZmLoginLog();
            dmLoginLog.Id = SequentialGuid.SequentialGuidGenerator.Instance.NewGuid().ToString("N");
            dmLoginLog.CreateBy = user.Id;
            dmLoginLog.CreateIp = ipAddress;
            dmLoginLog.CreateOn = DateTime.Now;
            _dbContext.Add(dmLoginLog);
            _dbContext.SaveChanges();
            return new RespRet<AuthenticateResponse>(true, "Ok"
                , new AuthenticateResponse(user.Id, jwtToken, refreshToken.Token, user.Nickname, user.Unitname));
        }


        public RespRet<AuthenticateResponse> Authenticate(string userNumber, string ipAddress)
        {

            var user = _userRepository.Query(c => c.Number == userNumber && c.IsDelete != 1 && c.Disabled != 1).FirstOrDefault();
            if (user == null)
            {
                return new RespRet<AuthenticateResponse>(true, "Ok"
                , new AuthenticateResponse(userNumber, "未找到用户","未找到用户","未找到用户", "未找到用户"));
            }
            if (string.IsNullOrEmpty(userNumber))
            {
                return new RespRet<AuthenticateResponse>(false, "userNumber is null");
            }

            var cached = _userService.GetRefreshTokens(userNumber);

            // authentication successful so generate jwt and refresh tokens
            var jwtToken = GenerateJwtToken(userNumber);
            var refreshToken = GenerateRefreshToken(userNumber, ipAddress);

            // save refresh token
            if (cached.Succ)
            {
                cached.Data.Tokens.Add(jwtToken.Sha256(), jwtToken);
                cached.Data.RefreshTokens.Add(refreshToken.Token.Sha256(), refreshToken);
                _userService.UpdateRefreshTokens(cached.Data);
            }
            else
            {
                var userRefreshToken = new UserRefreshToken(userNumber);
                userRefreshToken.Tokens.Add(jwtToken.Sha256(), jwtToken);
                userRefreshToken.RefreshTokens.Add(refreshToken.Token.Sha256(), refreshToken);
                _userService.UpdateRefreshTokens(userRefreshToken);
            }

            return new RespRet<AuthenticateResponse>(true, "Ok"
                , new AuthenticateResponse(userNumber, jwtToken, refreshToken.Token, user.Nickname, user.Unitname));
        }


        public RespRet<AuthenticateResponse> AuthenticateId(ZmUser user, string ipAddress)
        {
            var cached = _userService.GetRefreshTokens(user.Number);

            // authentication successful so generate jwt and refresh tokens
            var jwtToken = GenerateJwtToken(user.Number);
            var refreshToken = GenerateRefreshToken(user.Number, ipAddress);

            // save refresh token
            if (cached.Succ)
            {
                cached.Data.Tokens.Add(jwtToken.Sha256(), jwtToken);
                cached.Data.RefreshTokens.Add(refreshToken.Token.Sha256(), refreshToken);
                _userService.UpdateRefreshTokens(cached.Data);
            }
            else
            {
                var userRefreshToken = new UserRefreshToken(user.Number);
                userRefreshToken.Tokens.Add(jwtToken.Sha256(), jwtToken);
                userRefreshToken.RefreshTokens.Add(refreshToken.Token.Sha256(), refreshToken);
                _userService.UpdateRefreshTokens(userRefreshToken);
            }
            return new RespRet<AuthenticateResponse>(true, "Ok"
                , new AuthenticateResponse(user.Number, jwtToken, refreshToken.Token, user.Nickname, user.Unitname));
        }
        public RespRet<AuthenticateResponse> RefreshToken(string token, string ipAddress)
        {
            var user = _userService.GetRefreshTokens(token);

            // return null if no user found with token
            if (user == null)
            {
                return new RespRet<AuthenticateResponse>(false, "用户不存在");
            }

            if (!user.Succ)
            {
                return new RespRet<AuthenticateResponse>(false, user.Msg);
            }

            string key = token.Sha256();
            if (!user.Data.RefreshTokens.ContainsKey(key))
            {
                return new RespRet<AuthenticateResponse>(false, "刷新Token不存在");
            }
            var find = user.Data.RefreshTokens[key];
            // return null if token is no longer active
            if (!find.IsActive)
            {
                return new RespRet<AuthenticateResponse>(false, "刷新Token不可用");
            }

            var userNumber = GetUserNumber(token);

            // generate new jwt
            var jwtToken = GenerateJwtToken(user.Data.Id);
            // replace old refresh token with a new one and save
            var newRefreshToken = GenerateRefreshToken(userNumber, ipAddress);

            find.Revoked = DateTime.UtcNow;
            find.RevokedByIp = ipAddress;
            find.ReplacedByToken = newRefreshToken.Token;

            user.Data.Tokens.Add(jwtToken.Sha256(), jwtToken);
            user.Data.RefreshTokens.Remove(key);
            user.Data.RefreshTokens.Add(newRefreshToken.Token.Sha256(), newRefreshToken);

            _userService.UpdateRefreshTokens(user.Data);

            return new RespRet<AuthenticateResponse>(true, "Ok"
                , new AuthenticateResponse(user.Data.Id, jwtToken, newRefreshToken.Token,"", ""));
        }

        public RespRet RevokeToken(string token, string ipAddress)
        {
            _userService.RevokeToken(token);
            return new RespRet(true, "Ok");
        }

        // helper methods
        private string GenerateJwtToken(string userNumber)
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var claims = new[]
            {
                new Claim(ClaimTypes.Sid,userNumber)
            };

            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenManagement.Secret));
            var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            var jwtToken = new JwtSecurityToken(_tokenManagement.Issuer
                , _tokenManagement.Audience
                , claims
                , expires: DateTime.Now.AddMinutes(_tokenManagement.AccessExpiration)
                , signingCredentials: credentials);

            return tokenHandler.WriteToken(jwtToken);
        }

        private RefreshToken GenerateRefreshToken(string userNumber, string ipAddress)
        {
            using (var rngCryptoServiceProvider = new RNGCryptoServiceProvider())
            {
                var randomBytes = new byte[64];
                rngCryptoServiceProvider.GetBytes(randomBytes);
                var chars = $"{userNumber}.{UrlBase64.Encode(randomBytes, PaddingPolicy.Discard)}";
                return new RefreshToken
                {
                    Token = chars,
                    Expires = DateTime.Now.AddMinutes(_tokenManagement.RefreshExpiration),
                    Created = DateTime.Now,
                    CreatedByIp = ipAddress
                };
            }
        }

        public static string GetUserNumber(string refreshTokenOrUserNumber)
        {
            if (string.IsNullOrEmpty(refreshTokenOrUserNumber))
            {
                return "";
            }

            return refreshTokenOrUserNumber.Split('.')[0];
        }
    }
}
